iOS SIGKILL 信号量崩溃抓取以及优化实践
The following article is from 百度App技术 Author 落叶情思
一、什么是SIGKILL崩溃
GEEK TALK
很多时候,当我们在崩溃日志中看到 SIGKILL 关键信息的时候,这就表示操作系统从上层杀死了我们的进程,也就是我们常说的 kill -9 命令。
Exception Type: EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Reason: Namespace RUNNINGBOARD, Code 0xdead10cc
一般来说,Apple 崩溃日志里面通常都会包含应用程序被杀死的具体的原因。如上所示,Termination Reason 里面就包含了这个崩溃的错误代码 0xdead10cc,就表示应用程序挂起的时候发生了文件和数据库锁操作而被操作系统杀死。
二、怎么抓取SIGKILL崩溃
GEEK TALK
丨1 为什么SIGKILL不能被捕获
丨2 使用 MetricKit 框架捕获SIGKILL
丨2.1 Metrickit 是什么
丨2.2 使用Metrickit 收集 SIGKILL信号量 的好处
不需要注册信号量捕获回调函数
不需要时刻监控,只需冷启阶段注册获取一次就行
丨2.3 怎么使用 Metrickit 获取崩溃信息
丨2.3.1 添加 MetricKit 动态库依赖
丨2.3.2 注册 MetricKit 监听者
if (@available(iOS 14.0, *)) {
MXMetricManager *manager = [MXMetricManager sharedManager];
if (self && manager && [manager respondsToSelector:@selector(addSubscriber:)]) {
[manager addSubscriber:self];
}
}
丨2.3.3 监听者实现MXMetricManagerSubscriber协议方法,payloadDic里面包含着上次本应用发生的崩溃日志堆栈和信息
// 用户如果有崩溃数据,注册监听之后就会回调
- (void)didReceiveDiagnosticPayloads:(NSArray<MXDiagnosticPayload *> * _Nonnull)payloads API_AVAILABLE(ios(14.0)){
if (@available(iOS 14.0, *)) {
for (MXDiagnosticPayload *payload in payloads) {
NSDictionary *payloadDic = [payload dictionaryRepresentation];
});
}
}
}
丨2.3.4 当收到回调消息后,需要对关键信息做组装,获取崩溃堆栈和相关关键信息
NSArray *callStackRootFrames = [dicFrame ArrayValueForKey:@"callStackRootFrames"];
if (callStackRootFrames.count <= 0) {
continue;
}
NSDictionary *dicZero = [callStackRootFrames ObjectAtIndex:0];
int rootIndex = 0;
while (dicZero && dicZero.count > 0) {
//获取Image 的 UUID
NSString *binaryUUID = [dicZero stringValueForKey:@"binaryUUID"];
//获取Image 的 名称
NSString *binaryName = [dicZero stringValueForKey:@"binaryName"];
//获取Image 的加载地址
long long baseAdd = [[dicZero NumberValueForKey:@"offsetIntoBinaryTextSegment"] longLongValue];
//获取崩溃函数的地址
long long address = [[dicZero numberValueForKey:@"address"] longLongValue];
//看上一层调用堆栈的
NSArray *subFrames = [dicZero arrayValueForKey:@"subFrames"];
[strStack appendFormat:@"%d %@ 0x%llx 0x%llx + %lld\n", rootIndex, binaryName, baseAdd, address, address - model.baseAddress];
rootIndex++;
if (subFrames && subFrames.count >= 0) {
dicZero = [subFrames ObjectAtIndex:0];
} else {
dicZero = nil;
}
}
丨2.3.5 使用 Metrickit 收集崩溃的不足
只支持 iOS14 以后的崩溃日志收集;PS:MetricKit是iOS13开始有的框架,但是崩溃日志的支持是iOS14开始支持的。 崩溃日志没有返回具体的崩溃时间和启动时间,崩溃场景信息除了堆栈外没有其余信息,附加信息较少,需要另外的手段来收集
如果使用了段迁移编译技术,主程序 Mach-O 的加载地址和 uuid MetricKit无法给出正确的值,需要例外处理。可通过 Mach-O文件的LC-MAIN入口来获取主程序main函数的地址,从而算出加载其起始地址。
iOS14 的崩溃日志是24小时会回调通知一次,时效性低;iOS15 之后,崩溃日志会在下次启动之后就返回,但经验证,可能有例外情况。
三、SIGKILL日志中Code的含义解释
0x8badf00d:
0xc00010ff:
0xbaadca11:
0xbad22222:
0xc51bad01:
0xc51bad02:
四、百度App常见SIGKILL问题
丨4.1 主线程执行耗时操作太久
弱网下同步的网络请求 处理大量的数据的任务,比如大的JSON文件或者 3D 模型的加载和处理 触发大量的 Core Data 同步保存操作
触发大量的数据库操作等
主线程解码大图片,解压文件等操作
- (void)getContentArray:(void (^)(NSArray *resultArray))completeBlock {
dispatch_barrier_async(self.readWriteQueue, ^{
if (completeBlock) {
NSArray *resultArray = [NSArray arrayWithArray:self.array];
completeBlock(resultArray);
}
});
}
丨4.2 主线程和子线程死锁,陷入互相等待的循环
Thread 0 Crashed:
0 libsystem_kernel.dylib ___ulock_wait (in libsystem_kernel.dylib) 8
1 libdispatch.dylib __dlock_wait (in libdispatch.dylib) 56
2 libdispatch.dylib __dispatch_once_wait (in libdispatch.dylib) 120
3 BaiduBoxApp +[xxxConfig sharedInstance] (in BaiduBoxApp) 20
4 BaiduBoxApp +[xxxConfig updateABConfig] (in BaiduBoxApp) 0
5 BaiduBoxApp -[xxxManager startOnce] (in BaiduBoxApp) 20
Thread 33 :0 libsystem_kernel.dylib ___ulock_wait (in libsystem_kernel.dylib) 81 libdispatch.dylib __dlock_wait (in libdispatch.dylib) 562 libdispatch.dylib __dispatch_thread_event_wait_slow (in libdispatch.dylib) 563 libdispatch.dylib ___DISPATCH_WAIT_FOR_QUEUE__ (in libdispatch.dylib) 3644 libdispatch.dylib __dispatch_sync_f_slow (in libdispatch.dylib) 144.........9 BaiduBoxApp ___48+[xxxConfig sharedInstance]_block_invoke (in BaiduBoxApp) 010 libdispatch.dylib __dispatch_client_callout (in libdispatch.dylib) 2011 libdispatch.dylib __dispatch_once_callout (in libdispatch.dylib) 3212 BaiduBoxApp +[xxxConfig sharedInstance] (in BaiduBoxApp) 20
END